package kdata

import (
	"encoding/csv"
	"fmt"
	"os"
	"time"
)

/*
const (
	PERIOD_DAY
)
*/

type K struct {
	SymbolName   string
	Period       int
	Time         []int64
	Open         []float32
	High         []float32
	Low          []float32
	Close        []float32
	Volume       []float32
	Amount       []float32
	OpenInterest []float32
}

// K线数据的一段
type Segment struct {
	K     *K  // 原始k线数据
	Begin int // 片段起始下标，包含
	End   int // 片段末尾下标，不包含
}

// 主力连续合约k线数据
type ContinuousK struct {
	Origin   *K         // 拼接后的k线数据，原始价格
	Adjusted *K         // 拼接后的k线数据，经过价差调整，即向前复权
	Segments []*Segment // 原始k线数据的片段列表
	Indices  [][2]int   // 拼接后的索引，与Segments一一对应
}

type SwitchError struct {
}

type KMaker interface {
	Make() *K
}

func NewK(from *K) *K {
	size := len(from.Time)
	k := &K{
		from.SymbolName,
		from.Period,
		make([]int64, size),
		make([]float32, size),
		make([]float32, size),
		make([]float32, size),
		make([]float32, size),
		make([]float32, size),
		make([]float32, size),
		make([]float32, size),
	}
	copy(k.Time, from.Time)
	copy(k.Open, from.Open)
	copy(k.High, from.High)
	copy(k.Low, from.Low)
	copy(k.Close, from.Close)
	copy(k.Volume, from.Volume)
	copy(k.Amount, from.Amount)
	copy(k.OpenInterest, from.OpenInterest)
	return k
}

// 读取日线数据，格式:
// Time,Open,High,Low,Close,Volume,OpenInterest
// 2013/05/20,3724,3750,3719,3738,122,242
func readFromCsvDay(file string, symbol string, period int) (*K, error) {
	rd, err := os.Open(file)
	if err != nil {
		return nil, err
	}
	defer rd.Close()
	k := &K{
		symbol,
		period,
		[]int64{},
		[]float32{},
		[]float32{},
		[]float32{},
		[]float32{},
		[]float32{},
		[]float32{},
		[]float32{},
	}
	reader := csv.NewReader(rd)
	reader.Comment = '#'
	reader.FieldsPerRecord = 7
	records, err := reader.ReadAll()
	if err != nil {
		return nil, err
	}
	loc, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		return nil, err
	}
	for _, record := range records {
		t, err := time.ParseInLocation("2006/1/2", record[0], loc)
		if err != nil {
			return nil, err
		}
		k.Time = append(k.Time, t.Unix())
		var f float32
		_, err = fmt.Sscanf(record[1], "%f", &f)
		if err != nil {
			return nil, err
		}
		k.Open = append(k.Open, f)
		_, err = fmt.Sscanf(record[2], "%f", &f)
		if err != nil {
			return nil, err
		}
		k.High = append(k.High, f)
		_, err = fmt.Sscanf(record[3], "%f", &f)
		if err != nil {
			return nil, err
		}
		k.Low = append(k.Low, f)
		_, err = fmt.Sscanf(record[4], "%f", &f)
		if err != nil {
			return nil, err
		}
		k.Close = append(k.Close, f)
		_, err = fmt.Sscanf(record[5], "%f", &f)
		if err != nil {
			return nil, err
		}
		k.Volume = append(k.Volume, f)
		_, err = fmt.Sscanf(record[6], "%f", &f)
		if err != nil {
			return nil, err
		}
		k.OpenInterest = append(k.OpenInterest, f)
	}
	k.Amount = make([]float32, len(k.Time))
	return k, err
}

// 构造新的K实例，存放seg所定义的片段
// 对返回的*K进行操作，不会影响到seg.K
func segmentToK(seg *Segment) *K {
	size := seg.End - seg.Begin
	k := &K{
		seg.K.SymbolName,
		seg.K.Period,
		make([]int64, size),
		make([]float32, size),
		make([]float32, size),
		make([]float32, size),
		make([]float32, size),
		make([]float32, size),
		make([]float32, size),
		make([]float32, size),
	}
	copy(k.Time, seg.K.Time[seg.Begin:seg.End])
	copy(k.Open, seg.K.Open[seg.Begin:seg.End])
	copy(k.High, seg.K.High[seg.Begin:seg.End])
	copy(k.Low, seg.K.Low[seg.Begin:seg.End])
	copy(k.Close, seg.K.Close[seg.Begin:seg.End])
	copy(k.Volume, seg.K.Volume[seg.Begin:seg.End])
	copy(k.Amount, seg.K.Amount[seg.Begin:seg.End])
	copy(k.OpenInterest, seg.K.OpenInterest[seg.Begin:seg.End])
	return k
}

// 构造主力合约的连续k线数据，并进行价差调整，即向前复权
// data: 某一品种的各期合约的k线数据
func MakeContinuousK(symbol string, data []*K) (*ContinuousK, error) {
	if len(data) == 0 {
		return nil, nil
	}
	// kmap的value是一个map，time -> index
	kmap := make(map[*K]map[int64]int)
	for _, k := range data { // 每一个品种
		timeToIndex := make(map[int64]int)
		for i, t := range k.Time { // 每一根k线
			timeToIndex[t] = i
		}
		kmap[k] = timeToIndex
	}
	con := new(ContinuousK)
	domSeg := findDominant(kmap)
	delete(kmap, domSeg.K)
	for {
		next, err := switchDominant(domSeg, kmap)
		if err != nil {
			return nil, err
		}
		addDominant(con, domSeg)
		if next == nil {
			break
		}
		domSeg = next
		delete(kmap, domSeg.K)
	}
	con.Origin.SymbolName = symbol
	con.Adjusted.SymbolName = symbol
	return con, nil
}

func appendK(dst *K, k *K) {
	dst.Time = append(dst.Time, k.Time)
	dst.Open = append(dst.Open, k.Open)
	dst.High = append(dst.High, k.High)
	dst.Low = append(dst.Low, k.Low)
	dst.Close = append(dst.Close, k.Close)
	dst.Volume = append(dst.Volume, k.Volume)
	dst.Amount = append(dst.Amount, k.Amount)
	dst.OpenInterest = append(dst.OpenInterest, k.OpenInterest)
}

func adjustK(k *K, priceDiff float32) {
	for i := 0; i < len(k.Time); i++ {
		k.Open[i] += priceDiff
		k.High[i] += priceDiff
		k.Low[i] += priceDiff
		k.Close[i] += priceDiff
	}
}

// 将主力合约片段seg加入连续合约数据结构con中
// 价差调整的原则是，
// 经过价差调整后的k线序列，换月后第一根bar的收盘价 - 换月前最后一根bar的收盘价 =
// 所换主力合约对应的两根bar的收盘价之差
func addDominant(con *ContinuousK, seg *Segment) {
	segK := segmentToK(seg)
	if con.Origin == nil { // con未初始化
		con.Origin = segK
		con.Adjusted = NewK(segK)
		con.Segments = []*Segment{seg}
		con.Indices = [][2]int{
			[2]int{0, len(segK.Time)},
		}
		return
	}
	appendK(con.Origin, segK)
	last := len(con.Adjusted.Time) - 1 // 原连续合约最后一根bar的索引
	priceDiff := seg.K.Close[seg.Begin-1] - con.Adjusted.Close[last]
	adjustK(con.Adjusted, priceDiff)
	appendK(con.Adjusted, segK)
	con.Segments = append(con.Segments, seg)
	con.Indices = append(con.Indices, [2]int{last + 1, last + 1 + len(segK.Time)})
}

// 切换主力合约
// prev是旧的主力合约片段，prev.Begin已设置好，prev.End未设置
// 该函数将设置prev.End的值
// 以next返回新的主力合约片段，next.Begin已设置好，next.End未设置
// 如果已经没有下一个主力合约，则prev.End设置为len(prev.K.Time)，返回nil
func switchDominant(prev *Segment, kmap map[*K]map[int64]int) (next *Segment, err error) {
	for i := prev.Begin; i < len(prev.K.Time); i++ { // 遍历前一个主力合约的每一根bar
		t := prev.K.Time[i]
		for k, tToi := range kmap { // 查看每一个合约的对应bar
			index, ok := tToi[t]
			if !ok {
				continue
			}
			// 以成交量和持仓量决定是否切换
			if k.Volume[index] > 1.1*prev.K.Volume[i] &&
				k.OpenInterest[index] > 1.1*prev.K.OpenInterest[i] {
				prev.End = i
				next := &Segment{k, index}
				var err *SwitchError
				if prev.End >= len(prev.K.Time) ||
					next.Begin == 0 {
					err = &SwitchError{}
				}
				return next, err
			}
		}
	}
	// 已无下一个主力合约
	prev.End = len(prev.K.Time)
	return nil, nil
}

// 找出起始的主力合约数据片段
// kmap中，如果某个合约数据的第一根k线早于其他合约的第一根k线
// 则返回该合约的数据
// 如果有两个或以上合约数据同时存在最早一根k线，则以成交量大的为准
// 返回的Segment，Begin已设置，End未设置
func findDominant(kmap map[*K]map[int64]int) *Segment {
	var dom *Segment
	for k := range kmap {
		if dom == nil {
			dom = &Segment{K: k, Begin: 0}
			continue
		}
		if k.Time[0] < dom.K.Time[0] {
			dom.K = k
		} else if k.Time[0] == dom.K.Time[0] && k.Volume[0] > dom.K.Volume[0] {
			dom.K = k
		}
	}
	return dom
}
